package yu.ac.bg.etf.kdp.server;

import java.io.*;
import java.net.*;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import yu.ac.bg.etf.kdp.klase.*;

public class ServerThread extends Thread {

	private static int cnt = 0;
	private final int id;

	private Socket clientSocket;
	private Server server;

	public ServerThread(Server server, Socket clientSocket) {
		System.out.println("Server thread started..");
		this.clientSocket = clientSocket;
		this.server = server;
		id = cnt++;
	}

	public void run() {
		try {
			// prvo uzimamo tokove podataka iz socket-a
			OutputStream os = clientSocket.getOutputStream();
			InputStream is = clientSocket.getInputStream();
			ObjectOutputStream outo = new ObjectOutputStream(os);
			outo.flush();
			ObjectInputStream ino = new ObjectInputStream(is);
			BufferedOutputStream bos = new BufferedOutputStream(os);
			BufferedInputStream bis = new BufferedInputStream(is);
			// treba da otkrijemo ko nam je poslao zahtev
			Msg msg = (MsgTxt) ino.readObject();
			String str = (String) (msg.getBody());
			if (str.equals("cliRequest")) {
				// u pitanju je klijentski zahtev i treba
				// da proverimo da li je zahtev za obavljanjem
				// posla ili za rezultatima
				msg = (Msg) ino.readObject();
				String reqType = (String) msg.getBody();
				if (reqType.equals("newJob")) {
					// primamo komandu
					msg = (Msg) ino.readObject();
					String command = (String) msg.getBody();
					// primamo ime datoteke za rezultate
					msg = (Msg) ino.readObject();
					String results = (String) msg.getBody();
					// pravimo posao
					Job job = new Job(command, results, Job.JOB_READY);
					// primamo fajl a.txt
					FileTransfer.recieveFile(ino, "a" + job.getId() + ".txt");
					// da li treba da primamo fajl b.txt
					msg = (Msg) ino.readObject();
					String recB = (String) msg.getBody();
					if (recB.equals("sendingB")) {
						FileTransfer.recieveFile(ino, "b" + job.getId() + ".txt");
					} else if (!recB.equals("notSendingB")) {
						System.out.println("Bad client protocol message..");
						System.exit(1);
					}
					// sada treba pocao da ubacimo na listu poslova na
					// serveru
					outo.writeObject(job);
					outo.flush();
					// sada treba da nadjemo slobodnu radnu stanicu
					Node node;
					synchronized (server) {
						job.setStatus(job.JOB_SCHEDULED);
						server.insertNewJob(job);
						node = server.getFreeWorkstation();
						job.setMainWorkstation(node);
					}
					// kacimo se na njen socket koji slusa server
					Socket sock = new Socket(node.getHostForServer(), node.getPortForServer());
					ObjectOutputStream outo1 = new ObjectOutputStream(sock.getOutputStream());
					outo1.flush();
					ObjectInputStream ino1 = new ObjectInputStream(sock.getInputStream());
					
					outo1.writeObject(new MsgTxt("serverRequest"));
					outo1.flush();
					outo1.writeObject(new MsgTxt("jobRequest"));
					outo1.flush();
					outo1.writeObject(job);
					outo1.flush();
					FileTransfer.sendFile(outo1, "a" + job.getId() + ".txt");
					if (recB.equals("sendingB"))
						FileTransfer.sendFile(outo1, "b" + job.getId() + ".txt");
					job.setStatus(Job.JOB_RUNNING);
					FileOutputStream file = new FileOutputStream("server_log.txt",true);
					PrintWriter log = new PrintWriter(file);
					log.println("\n");
					log.println(">>Job Id 				:	"+job.getId());
				    DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
				    Date date = new Date();
					log.println(">>Job recieved			:	"+dateFormat.format(date));
					log.println(">>Job distributed to	:	"+node.getWstaServerHost());
					log.close();
					ino1.close();
					outo1.close();
					sock.close();
					outo.close();
					ino.close();
					clientSocket.close();
				} else if (reqType.equals("getResults")) {
					// klijent trazi rezultate od servera
					// treba da proverimo da li na serveru
					// uopste postoji posao koji je zadao
					// klijent
					msg = (Msg) ino.readObject();
					int jobId = Integer.parseInt((String)msg.getBody());
					System.out.println("\n JOB ID WHOSE RESULTS WE ARE GETTING IS : "+jobId);
					synchronized (server) {
						Job j = ((JobNode) server.jobList.get(server.jobList.getIndexOf(jobId))).getJob();
						Node n = j.getMainWorkstation();
						if (server.jobList.getIndexOf(jobId) == -1) {
							outo.writeObject(new MsgTxt("jobNotExists"));
							outo.flush();
						}
						else {
							// posao postoji na serveru
							outo.writeObject(new MsgTxt("jobExists"));
							outo.flush();
							// treba da sacekamo da se posao odradi
							int index = server.jobList.getIndexOf(jobId);
							int status = ((JobNode) server.jobList.get(index))
									.getJob().getStatus();
							while (status != Job.JOB_DONE)
								try {
									server.wait();
									status = ((JobNode) server.jobList.get(index))
									.getJob().getStatus();
								} catch (InterruptedException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
							outo.writeObject(new MsgTxt("jobDone"));
							outo.flush();
							outo.writeObject(j);
							outo.flush();
							String fname = ((JobNode) server.jobList.get(index))
									.getJob().getResults();
							FileTransfer.sendFile(outo,""+jobId+fname);
							Job job = ((JobNode) server.jobList.remove(index)).getJob();
							server.serverGUI.getJobList().remove(
									"Job " + job.getId());
						}
					}
					outo.close();
					ino.close();
					clientSocket.close();
				} else if (reqType.equals("abortJob")) {
					msg = (Msg) ino.readObject();
					int jobId = Integer.parseInt((String)msg.getBody());
					synchronized (server) {
						System.out.println("CHECK..");
						if (server.jobList.getIndexOf(jobId) == -1) {
							outo.writeObject(new MsgTxt("jobNotExists"));
							outo.flush();
						}
						else {
							// posao postoji na serveru
							outo.writeObject(new MsgTxt("jobExists"));
							outo.flush();
							// treba da obrisemo posao iz liste poslova
							int ind = server.jobList.getIndexOf(jobId);
							server.jobList.remove(ind);
							server.serverGUI.getJobList().remove("Job "+jobId);
							outo.writeObject(new MsgTxt("jobAborted"));
							outo.flush();
						}
					}
					outo.close();
					ino.close();
					clientSocket.close();
				} else {
					System.out.println("Bad client protocol message..");
					System.exit(1);
				}
			} else if (str.equals("wstaRequest")) {
				// zahtev je stigao od radne stanice i sada treba da proverimo
				// koji je to zahtev
				msg = (Msg) ino.readObject();
				str = (String) msg.getBody();
				if (str.equals("register")) {
					// postupamo po protokolu
					Node node = (Node) ino.readObject();
					synchronized (server) {
						server.workstations.add(node);
						server.serverGUI.getWstaList().add("Workstation "+node.getHostForServer());
						System.out.println("Workstation listed in server evidention..");
						server.notifyAll();
						outo.writeObject(new MsgTxt("ack"));
						outo.flush();
						// sad bi trebali da kreiramo nit za proveru ispravnosti
						CheckerThreadServer cts = new CheckerThreadServer(server,node);
						cts.start();
						server.checkersList.add(cts);
					}
					// zatvaramo komunikaciju
					outo.close();
					ino.close();
					clientSocket.close();
				} else if (str.equals("getNods")) {
					synchronized (server) {
						outo.writeObject(server.workstations);
						outo.flush();
					}
					outo.close();
					ino.close();
					clientSocket.close();
				} else if (str.equals("sendingResults")) {
					Job job = (Job) ino.readObject();
					synchronized (server) {
						// treba da proverimo je li posao za koji su stigli
						// rezultati u evidenciji poslova i da li je
						// posao uopste odradjen ili nije uspeo da se odradi
						if (server.jobList.getIndexOf(job.getId()) == -1) {
							// posao nije na serveru (abortiran)
							System.out.println("Job for which results came is not listed on server..");
							outo.writeObject(new MsgTxt("jobAborted"));
							outo.flush();
						} else if (job.getStatus() == Job.JOB_FAILED) {
							// posao nije uspeo i treba da azuriramo listu poslova
							outo.writeObject(new MsgTxt("jobNotAborted"));
							outo.flush();
							int index = server.jobList.getIndexOf(job.getId());
							((JobNode) server.jobList.get(index)).getJob()
									.setStatus(Job.JOB_FAILED);
						} else {
							// primamo fajl sa rezultatima
							outo.writeObject(new MsgTxt("jobNotAborted"));
							outo.flush();
							FileTransfer.recieveFile(ino, ""+job.getId()+job.getResults());
							// treba da azuriramo ulaz u listi poslova na serveru
							int index = server.jobList.getIndexOf(job.getId());
							server.jobList.set(index, new JobNode(job));
							// i da obavestimo sve druge niti o prispecu
							// obavljenog posla na server
						}
						Node w;
						Node m = job.getMainWorkstation();
						for (int i = 0;i < server.workstations.size();i++) {
							System.out.println("WORKSTATION CAPACITY UPDATING..");
							w = (Node)server.workstations.get(i);
							if (m.getHostForServer().equals(w.getHostForServer())
								&& m.getPortForServer() == w.getPortForServer()) {
								System.out.println("WORKSTATION CAPACITY UPDATED..");
								w.setJobCapacity(w.getJobCapacity() + 1);
							} 
						}
						
						JobNode j;
						for (int i = 0;i < server.jobList.size();i++) {
							j = (JobNode)server.jobList.get(i);
							if (job.getId() == j.getJob().getId()) {
								System.out.println("JOB STATUS UPDATED..");
								j.getJob().setStatus(Job.JOB_DONE);
							} 
						}
						
						FileOutputStream file = new FileOutputStream("server_log.txt",true);
						PrintWriter log = new PrintWriter(file);
						DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
					    Date date = new Date();
					    log.println("\n");
						log.println(">>Job results recieved 	:	"+dateFormat.format(date));
						log.println(">>Job Id is				:	"+job.getId());
						log.println(">>Job status is			:	"+Job.statusToString(job.getStatus()));
						log.close();
						server.notifyAll();
					}
					outo.close();
					ino.close();
					clientSocket.close();
				} else if (str.equals("wstaConnect")) {System.out.println("\nwstaConnect request!");
					NodeList nodeList = (NodeList)ino.readObject();
					msg = (Msg) ino.readObject();
					String h = (String)msg.getBody();
					msg = (Msg) ino.readObject();
					int p = Integer.parseInt((String)msg.getBody());
					// treba da posaljemo zahteve za uspostavljanjem komunikacije
					// svim ovim stanicama iz liste
					for (int i = 0;i < nodeList.size();i++) {
						Node node = (Node)nodeList.get(i);
						if (!(node.getWstaServerHost().equals(h) && node.getWstaServerPort() == p)) {
							Socket sock = new Socket(node.getHostForServer(),
									node.getPortForServer());
							ObjectOutputStream outo1 = new ObjectOutputStream(
									sock.getOutputStream());
							outo1.flush();
							ObjectInputStream ino1 = new ObjectInputStream(sock
									.getInputStream());
							outo1.writeObject(new MsgTxt("serverRequest"));
							outo1.flush();
							outo1.writeObject(new MsgTxt("wstaConnect"));
							outo1.flush();
							outo1.writeObject(nodeList);
							outo1.flush();
							outo1.close();
							ino1.close();
							sock.close();
						}
					}
					outo.close();
					ino.close();
					clientSocket.close();
				} else {
					// greska
					System.out.println("Bad workstation-server protocol message..");
					outo.close();
					ino.close();
					clientSocket.close();
					System.exit(1);
				}
			} else {
				// greska
				System.out.println("Bad server request protocol message..");
				outo.close();
				ino.close();
				clientSocket.close();
				System.exit(1);
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			System.out.println("Connection failed on server..");
		}
		System.out.println("Server thread finished running..");
	}

}
